package cn.kk.gateway.filter;

import cn.hutool.core.util.StrUtil;
import cn.kk.base.model.Payload;
import cn.kk.base.model.Result;
import cn.kk.code.ErrorCodeEnum;
import cn.kk.constant.FileConstant;
import cn.kk.constant.HeaderConstant;
import cn.kk.model.auth.vo.PayloadUserVO;
import cn.kk.utils.JwtUtils;
import cn.kk.utils.RSAUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Objects;

/**
 * 鉴权过滤器
 * @author liujunkai
 * @date 2023/8/7 15:04
 */
@Slf4j
@Order(-1)
@Component
public class AuthFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 判断地址是合法
        String path = request.getURI().getPath();
        // 取第一个/后的字符串判断
        path = StrUtil.subAfter(path, "/", false);
        // 预防越权攻击，如/..auth/
        if (path.contains(".")) {
            log.error("[鉴权网关]：捕获越权攻击 = {}", path);
            // 响应授权失败，不放行
            return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0301);
        }
        // 认证服务下的请求放行，除刷新token接口
        if (path.startsWith("auth") && !path.contains("refresh")) {
            return chain.filter(exchange);
        }
        // 判断是否持有token以及token是否有效
        String token = request.getHeaders().getFirst(HeaderConstant.TOKEN_HEADER);
        if (StringUtils.isEmpty(token)) {
            log.error("[鉴权网关]：缺少token = {}", path);
            // 响应授权失败，不放行
            return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0301);
        }
        try {
            String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource(FileConstant.PubKey)).getPath();
            Payload<PayloadUserVO> infoFromToken = JwtUtils.getInfoFromToken(token, RSAUtil.readPublicKey(filePath), PayloadUserVO.class);
            // 取出uid放入请求头中，方便后续过滤器使用
            Long uid = infoFromToken.getInfo().getUid();
            if (uid == null) {
                throw new RuntimeException("载荷用户id获取失败");
            }
            request.mutate().header(HeaderConstant.UID_HEADER, uid.toString());
            // 放行
            return chain.filter(exchange);
        } catch (Exception e) {
            log.error("[鉴权网关]：验签异常 = {};path = {}", e.getMessage(), path);
            // SignatureException 验签失败 | ExpiredJwtException 令牌过期
            // 响应授权失败，不放行
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return errorResponse(response, ErrorCodeEnum.USER_ERROR_A0311);
        }
    }

    /**
     * 错误响应
     * @param response  响应对象
     * @param errorCode 错误码
     * @return 错误响应
     */
    private static Mono<Void> errorResponse(ServerHttpResponse response, ErrorCodeEnum errorCode) {
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        byte[] jsonBytes = JSON.toJSONBytes(Result.error(errorCode));
        return response.writeWith(Mono.just(response.bufferFactory().wrap(jsonBytes)));
    }
}